from config.logger import setup_logging
import os
import re
import time
import random
import asyncio
import difflib
import traceback
import wave
import threading
import json
from pathlib import Path
from core.utils import p3
from core.handle.sendAudioHandle import send_stt_message
from plugins_func.register import register_function, ToolType, ActionResponse, Action
from extend.HeMusicPlatform import MusicAPI
from extend.GetBiQuGeContent import BiQuGeAPI
from core.utils.dialogue import Message
from core.providers.tts.dto.dto import TTSMessageDTO, SentenceType, ContentType
from concurrent.futures import ThreadPoolExecutor



TAG = __name__

BOOK_CACHE = {}

# 在模块顶部声明全局线程池
executor = ThreadPoolExecutor(max_workers=4)  # 根据需求调整线程数

play_book_function_desc = {
    "type": "function",
    "function": {
        "name": "play_book",
        "description": "听书的方法。",
        "parameters": {
            "type": "object",
            "properties": {
                "book_name": {
                    "type": "string",
                    "description": "小说名称，如果用户没有指定具体小说则为'random', 明确指定的时返回小说的名字 示例: ```用户:我想听星辰变小说\n参数：星辰变``` ```用户:播放小说 \n参数：random ```",
                }
            },
            "required": ["book_name"],
        },
    },
}


@register_function("play_book", play_book_function_desc, ToolType.SYSTEM_CTL)
def play_book(conn, book_name: str):
    try:
        book_intent = (
            f"播放小说 {book_name}" if book_name != "random" else "随机播放小说"
        )


        # 检查事件循环状态
        if not conn.loop.is_running():
            conn.logger.bind(tag=TAG).error("事件循环未运行，无法提交任务")
            return ActionResponse(
                action=Action.RESPONSE, result="系统繁忙", response="请稍后再试"
            )

        # 提交异步任务
        future = asyncio.run_coroutine_threadsafe(
            #真正处理小说部分
            handle_book_command(conn, book_intent), conn.loop
        )

        # 非阻塞回调处理
        def handle_done(f):
            try:
                f.result()  # 可在此处理成功逻辑
                conn.logger.bind(tag=TAG).info("播放完成")
            except Exception as e:
                conn.logger.bind(tag=TAG).error(f"播放失败: {e}")

        future.add_done_callback(handle_done)

        return ActionResponse(
            action=Action.NONE, result="指令已接收", response="正在为您播放小说"
        )
    except Exception as e:
        conn.logger.bind(tag=TAG).error(f"处理小说意图错误: {e}")
        return ActionResponse(
            action=Action.RESPONSE, result=str(e), response="播放小说时出错了"
        )


def _extract_book_name(text):
    """从用户输入中提取歌名"""
    for keyword in ["播放小说"]:
        if keyword in text:
            parts = text.split(keyword)
            if len(parts) > 1:
                return parts[1].strip()
    return None


def get_books_wav(potential_book, book_files):
    best_match = []

    # 先去现有的音频文件列表中获取
    for book_file in book_files:
        book_info = os.path.splitext(book_file)[0]
        book_info_parts = book_info.split("\\")
        if potential_book == book_info_parts[0]:
            best_match.append(BOOK_CACHE['book_dir'] + '\\' + book_file)
    # 章节排序
    if len(best_match) !=0:
        best_match = sorted(best_match, key=sort_book_title)
    return best_match,len(best_match)


def _find_best_match(potential_book, book_files,conn):
    """查找最匹配的书籍"""
    #现有小说文件列表中没有，要下载小说
    best_match,total = get_books_wav(potential_book,book_files)

    if len(best_match) == 0 :
        conn.logger.bind(tag=TAG).info("小说资源下载中")

        best_match,total = BiQuGeAPI.main(potential_book)
        if best_match is not None:
            try:
                # 这里需要修改下 返回值应该是改成名字
                conn.logger.bind(tag=TAG, file_name=best_match).info("音频资源下载成功")
            except Exception as e:
                conn.logger.bind(tag=TAG).error(f"处理音频文件名失败: {str(e)}")
                return None
        else:
            conn.logger.bind(tag=TAG).warning("未找到匹配的小说资源")

    # todo 还要开个线程检查数据库章节和已经合成完的音频对比，如果还有没合成完的章节那么就开始合成
    # 启动后台线程检查并合成缺失章节
    executor.submit(BiQuGeAPI.after_synthesis_audio, potential_book)

    return best_match,total


def get_book_files(book_dir, book_ext):
    book_dir = Path(book_dir)
    book_files = []
    book_file_names = []
    for file in book_dir.rglob("*"):
        # 判断是否是文件
        if file.is_file():
            # 获取文件扩展名
            ext = file.suffix.lower()
            # 判断扩展名是否在列表中
            if ext in book_ext:
                # 添加相对路径
                book_files.append(str(file.relative_to(book_dir)))
                book_file_names.append(
                    os.path.splitext(str(file.relative_to(book_dir)))[0]
                )
    return book_files, book_file_names


def initialize_book_handler(conn):
    global BOOK_CACHE
    if BOOK_CACHE == {}:
        if "play_book" in conn.config["plugins"]:
            BOOK_CACHE["book_config"] = conn.config["plugins"]["play_book"]
            BOOK_CACHE["book_dir"] = os.path.abspath(
                BOOK_CACHE["book_config"].get("book_dir", "./book_audio")  # 默认路径修改
            )
            BOOK_CACHE["book_ext"] = BOOK_CACHE["book_config"].get(
                "book_ext", (".mp3", ".wav", ".p3")
            )
            BOOK_CACHE["refresh_time"] = BOOK_CACHE["book_config"].get(
                "refresh_time", 60
            )
        else:
            BOOK_CACHE["book_dir"] = os.path.abspath("./book_dir")
            BOOK_CACHE["book_ext"] = (".mp3", ".wav", ".p3")
            BOOK_CACHE["refresh_time"] = 60
        # 获取书籍文件列表
        BOOK_CACHE["book_files"], BOOK_CACHE["book_file_names"] = get_book_files(
            BOOK_CACHE["book_dir"], BOOK_CACHE["book_ext"]
        )
        BOOK_CACHE["scan_time"] = time.time()
    return BOOK_CACHE


async def handle_book_command(conn, text):
    # 初始化字典值
    initialize_book_handler(conn)
    global BOOK_CACHE

    """处理播放有声小说指令"""
    clean_text = re.sub(r"[^\w\s]", "", text).strip()
    conn.logger.bind(tag=TAG).debug(f"检查是否是音乐命令: {clean_text}")

    # 尝试匹配具体小说名称
    if os.path.exists(BOOK_CACHE["book_dir"]):
        #小说名都是 章节-第x段.wav并且一章都是一段一段的，怎么能实现无缝衔接呢？
        if time.time() - BOOK_CACHE["scan_time"] > BOOK_CACHE["refresh_time"]:
            # 刷新小说文件列表
            BOOK_CACHE["book_files"], BOOK_CACHE["book_file_names"] = (
                get_book_files(BOOK_CACHE["book_dir"], BOOK_CACHE["book_ext"])
            )
            BOOK_CACHE["scan_time"] = time.time()

        potential_book = _extract_book_name(clean_text)
        if potential_book:
            best_match,total = _find_best_match(potential_book, BOOK_CACHE["book_files"],conn)
            if best_match:
                conn.logger.bind(tag=TAG).info(f"找到最匹配的小说: {best_match}")
                await play_local_book(conn, total=total,specific_file=best_match)
                return True
    # 检查是否是通用播放小说命令
    await play_local_book(conn)
    return True


def _get_random_play_prompt(book_name):
    """生成随机播放引导语"""
    # 移除文件扩展名
    clean_name = os.path.splitext(book_name)[0]
    prompts = [
        f"正在为您播放，{clean_name}",
        f"请欣赏有声小说，{clean_name}",
        f"即将为您播放，{clean_name}",
        f"为您带来，{clean_name}",
        f"让我们聆听，{clean_name}",
        f"接下来请欣赏，{clean_name}",
        f"为您献上，{clean_name}",
    ]
    # 直接使用random.choice，不设置seed
    return random.choice(prompts)

#给小说章节排序
def sort_book_title(file):
    name = file.split("\\")[-1].split(".wav")[0]
    chapter, segment = name.split("-")

    # 判断章节类型（序章/数字章节/其他）
    if "序章" in chapter:
        chapter_priority = (0, chapter)  # 序章优先级最高
    elif "第" in chapter and "章" in chapter:
        num = int(re.search(r"第(\d+)章", chapter).group(1))
        chapter_priority = (1, num)  # 数字章节按数字排序
    else:
        chapter_priority = (2, chapter)  # 其他章节（如"小不点"）

    # 提取段落数字
    segment_num = int(re.search(r"第(\d+)段", segment).group(1))
    return (*chapter_priority, segment_num)

async def play_local_book(conn, total=None,specific_file=None):
    global BOOK_CACHE
    """播放本地小说音频文件"""
    try:
        if not os.path.exists(BOOK_CACHE["book_dir"]):
            conn.logger.bind(tag=TAG).error(
                f"小说目录不存在: " + BOOK_CACHE["book_dir"]
            )
            return
        title = ''
        #total 这个是合成完该章节的总分割数 可以从1开始进行循环
        base_path = specific_file[0]
        prefix = base_path.replace("第1段", "第{}段")  # 把"1"换成"{}"以便format替换
        after_specific_file = [prefix.format(i) for i in range(1, total+1)]

        for index, file in enumerate(after_specific_file):
            print("当前播放音频:", file)
            parts = file.split("book_audio\\")
            # 提取后面路径的第一个部分，就是“书名”
            book_name = parts[1].split("\\")[0]
            #章节名
            chapter_name = parts[1].split("\\")[1].split("-")[0]

            text = _get_random_play_prompt(book_name+chapter_name)
            await send_stt_message(conn, text)
            conn.dialogue.put(Message(role="assistant", content=text))

            # 前提：通过 TTSMessageDTO 把语音提示和小说的音频文件推入 tts_text_queue 实现完整播报，
            # 这块有点不一样就是我需要循环播放 类似于歌曲循环效果，小说才能一章接着一章的放，但是有点问题，能不能进行长时间进行播放？
            # 1. 推送首句话
            conn.tts.tts_text_queue.put(
                TTSMessageDTO(
                    sentence_id=conn.sentence_id,
                    sentence_type=SentenceType.FIRST,
                    content_type=ContentType.ACTION,
                )
            )
            if title != chapter_name:
                title = chapter_name
                #2. 推送 应该是正在为您播放xxx小说 例如 正在为您播放第一章 秦羽
                conn.tts.tts_text_queue.put(
                    TTSMessageDTO(
                        sentence_id=conn.sentence_id,
                        sentence_type=SentenceType.MIDDLE,
                        content_type=ContentType.TEXT,
                        content_detail=text,
                    )
                )
            else:
                title = chapter_name

            # 3 推送小说路径给播放端
            # 这里是将音频的路径推送进tts的队列中，所以这里是音频发送者，实际播放在另外一段进行消费。
            #这里要进行循环播放指定的音频，直到这个文件夹所有的音频都结束。
            #
            conn.tts.tts_text_queue.put(
                TTSMessageDTO(
                    sentence_id=conn.sentence_id,
                    sentence_type=SentenceType.MIDDLE,
                    content_type=ContentType.FILE,
                    content_file=file,
                )
            )
            #获得该音频的总时长
            duration = get_audio_duration_wave(file)
            print(f"等待{duration}秒，模拟播放")
            # 非阻塞方式
            await asyncio.sleep(duration)
            #if index == 0:
            if index == len(after_specific_file) - 1:
                print("这是最后一次循环：",index)
                #播放后续章节
                await play_next_local_book(conn,book_name,chapter_name)

        # 4. 推送最后一句话
        conn.tts.tts_text_queue.put(
            TTSMessageDTO(
                sentence_id=conn.sentence_id,
                sentence_type=SentenceType.LAST,
                content_type=ContentType.ACTION,
            )
        )

    except Exception as e:
        conn.logger.bind(tag=TAG).error(f"播放音乐失败: {str(e)}")
        conn.logger.bind(tag=TAG).error(f"详细错误: {traceback.format_exc()}")
def get_audio_duration_wave(file_path):
    with wave.open(file_path, 'r') as audio_file:
        frames = audio_file.getnframes()
        rate = audio_file.getframerate()
        duration = frames / float(rate)
        # 四舍五入到整数
        return round(duration)

#当前章节的下一章
async def play_next_local_book(conn,book_name,title):
    #todo 如果数据库还没完整的合成，怎么办？
    next_audio_book = BiQuGeAPI.api_search_next_audio_book_database(book_name,title)
    synthetic_audio_url_str = next_audio_book[0]['synthetic_audio_url']
    synthetic_audio_url = []
    if synthetic_audio_url_str == '' or synthetic_audio_url_str == None:
        #todo 去文件列表中查找对应的数据对应的章节
        #todo 那么就有另一个问题了，文件夹的播放完毕了 那么就得去数据库中查询
        #   然后还得和数据库查询的结果和当前播放的文件集合进行对比，去掉当前播放的文件列表中的数据，
        #todo 凭什么知道播放的章节还没合成完呢？
        #todo
        print()
    else:
        synthetic_audio_url = json.loads(synthetic_audio_url_str)

    await play_local_book(conn,len(synthetic_audio_url),synthetic_audio_url)




